﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace PServiceBus.Serializer.Xml {
    public static class XmlSerializer {
        static readonly ConcurrentDictionary<Type, object> _serializers = new ConcurrentDictionary<Type, object>();
        static readonly ConcurrentDictionary<Type, MethodInfo> _deserializeMethods = new ConcurrentDictionary<Type, MethodInfo>();
        static readonly ConcurrentDictionary<Type, MethodInfo> _serializeMethods = new ConcurrentDictionary<Type, MethodInfo>();
        static readonly ConcurrentDictionary<Type, Func<object, string, object>> _deserializeDelegates =
            new ConcurrentDictionary<Type, Func<object, string, object>>();
        static readonly ConcurrentDictionary<Type, Func<object, object, string>> _serializeDelegates =
            new ConcurrentDictionary<Type, Func<object, object, string>>();
        static readonly string _deserializeStr = "Deserialize", _serializeStr = "Serialize";
        static readonly Type _objectType = typeof(object);
        private readonly static object _lockObject = new object();


        private static Func<object, string, object> GetDeserializeDelegate(this MethodInfo methodInfo) {
            var key = methodInfo.DeclaringType;
            return _deserializeDelegates.GetOrAdd(key, type =>
            {
                var methodParameters = methodInfo.GetParameters();
                var parameters = new List<ParameterExpression>();
                foreach (var param in methodParameters)
                    parameters.Add(Expression.Parameter(param.ParameterType, param.Name));
                var instance = Expression.Parameter(_objectType, "i");
                return Expression.Lambda<Func<object, string, object>>(Expression.Call(
                    Expression.Convert(instance, methodInfo.DeclaringType), methodInfo,
                    parameters.ToArray<Expression>()),
                    new ParameterExpression[] { instance }.Union(parameters)).Compile();
            });
        }

        private static Func<object, object,string> GetSerializeDelegate(this MethodInfo methodInfo) {
            var key = methodInfo.DeclaringType;
            return _serializeDelegates.GetOrAdd(key, type =>
            {
                var methodParameters = methodInfo.GetParameters();
                var instanceType = methodInfo.DeclaringType;
                var paramType = instanceType.GetGenericArguments()[0];
                var parameters = new List<ParameterExpression>();
                foreach (var param in methodParameters)
                    parameters.Add(Expression.Parameter(_objectType, param.Name));
                var instance = Expression.Parameter(_objectType, "i");
                return Expression.Lambda<Func<object, object, string>>(Expression.Call(
                    Expression.Convert(instance, instanceType), methodInfo,
                    parameters.Select(x => Expression.Convert(x, paramType))),
                    new ParameterExpression[] { instance }.Union(parameters)).Compile();
            });
        }

        internal static IXmlSerializer<T> GetSerializer<T>() {
            var type = typeof(T);
            return (IXmlSerializer<T>)GetSerializer(type);
        }

        internal static object GetSerializer(Type type) {
            var serializer = default(object);
            if (!_serializers.TryGetValue(type, out serializer)) {
                var serializerType = Generator.Generate(type);
                serializer = _serializers[type] =
                    Activator.CreateInstance(serializerType);
            }
            return serializer;
        }

        public static void Build() {
            Generator.Build();
        }

        public static void Prepare<T>() {
            GetSerializer<T>();
        }

        public static void Prepare(Type type) {
            GetSerializer(type);
        }

        public static string Serialize<T>(T value) {
            lock (_lockObject)
                return GetSerializer<T>().Serialize(value);
        }

        public static T Deserialize<T>(string xml) {
            return GetSerializer<T>().Deserialize(xml);
        }
        
        public static string Serialize(object value) {
            lock (_lockObject) {
                var type = value.GetType();
                object serializer = GetSerializer(type);
                var serializerType = serializer.GetType();
                var serializeMethod = _serializeMethods.GetOrAdd(type, key => serializerType.GetInterfaces()[0].GetMethod(_serializeStr));
                return serializeMethod.GetSerializeDelegate()(serializer, value);
            }
        }

        public static object Deserialize(string xml, Type type) {
            var serializer = GetSerializer(type);
            var serializerType = serializer.GetType();
            var deserializeMethod = _deserializeMethods.GetOrAdd(type, key => serializerType.GetInterfaces()[0].GetMethod(_deserializeStr));
            return deserializeMethod.GetDeserializeDelegate()(serializer, xml);
        }
    }
}
